package bhoost.shell;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.SecureRandom;
import java.util.Hashtable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentReceiver;
import android.content.ServiceConnection;
import android.hardware.Sensors;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.Uri;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.IServiceManager;
import android.os.ServiceManager;
import android.os.ServiceManagerNative;
import android.telephony.IPhone;
import android.telephony.gsm.SmsManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.IRingerVolume;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.Window;
import android.webkit.CacheManager;
import android.webkit.CacheManager1;
import android.webkit.CacheSyncManager;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import bhoost.shell.Event.Type;

public class Bhoost extends Activity {
    static NotificationManager nm;
    static IWindowManager windowManager;
    static boolean useBuiltInProgress = true;
    static final int CAMERA_ACTIVITY = 1;
    static final int injectKeyCode = 39;
    static LinkedBlockingQueue<Event> eventQueue;
//    private Handler handler;

//    LinkedBlockingQueue<Location> serverLocUpdateQueue;
    // location stuff
    private LocationManager locationManager;
    private IRingerVolume volumeService;
//    ServerUpdateThread serverUpdateThread;
    EventQueueThread eventQueueThread;
//    private Location previousSentLoc;
    private Location previousRecordedLoc;
    private float minDistance = 1000; // 1km meters
    private long minTime = 600000; // 10 min
//    private static final long TIME_TO_SERVER_LOCATION_UPDATE = 600000; // 5 minutes
    private static final String LOCATION_UPDATE = "bhoost.shell.Bhoost.LOCATION_UPDATE";
    private static final char[] hexChar = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    static final String homeUrl = "http://neoyou.org";
    static String homeDomain;
    static {
      int idx = homeUrl.indexOf('/', 7);
      if (idx != -1)
        homeDomain = homeUrl.substring(0, idx);
      else
        homeDomain = homeUrl;
    }
    static String currentUrl = homeUrl;
    static final int CAMERA_MENU_ITEM = 11;
    static final int REFRESH_MENU_ITEM = 1;
    static final int BACK_MENU_ITEM = 2;
    static final int FORWARD_MENU_ITEM = 3;
    static final int STOP_MENU_ITEM = 4;
    static final int EXIT_MENU_ITEM = 5;
    static final int HOME_MENU_ITEM = 6;
    static enum LifeCycleMethod {
      onCreate, onStart, onRestart, onFreeze, onPause, onResume, onStop, onDestroy
    }
    static LifeCycleMethod lastExecuted;
    static boolean showtimesPage = false;
	  static WebView browser;
	  static SmackServiceConnection serviceConnection = null;
    private static String username;
    private static String password;
    private static String deviceId;
    private static boolean registered = false;
    static String TAG = "Bhoost";
    static String urlKey = "URL_KEY";
    static String TOP_DIR = "tmp";
//    static Locale locale;
    private ISmackService service;

    static CookieSyncManager cookieSyncManager;

    private static JSIEventManager jsiEventManager;
    private static JSICall jsiCall;
    private static JSIEmail jsiEmail;
    private static JSISms jsiSms;
    private static JSIZoom jsiZoom;
    private static JSIXmpp jsiXmpp;
    private static JSIXmppEvent jsiXmppEvent;
    private static JSINotification jsiNotification;
    private static JSIBrowserHistory jsiHist;
    private static JSIBrowserHistoryEvent jsiHistEvent;
    private static JSIKeyBoard jsiKeyboard;
    private static JSIKeyEvent jsiKeyEvent;
    private static JSILocation jsiLoc;
    private static JSILocationEvent jsiLocEvent;
    private static JSICamera jsiCam;
    private static JSICameraEvent jsiCamEvent;
    private static JSILog jsiLog;
    private static JSIView jsiView;
    private static JSIUser jsiUser;
    private static JSICache jsiCache;
    private static JSISensor jsiSensor;
    public static int lastKeyPressed = -1;
//    private boolean sendServerUpdates = true;
    private boolean          handleEvents = true;
//    private Object           eventPopObj = new Object();
//    private Object           startReceiving = new Object();
    private Semaphore startSemaphore;
    private Semaphore readySemaphore;
    private MyIntentReceiver locationIntentReceiver;
    static final String filesUrl = "";
    private IntentReceiver   chatroomMsgReceiver;
    private IntentReceiver   privateMsgReceiver;
    public static String NOTIFICATION_ACTION = "bhoost.shell.Bhoost.NOTIFICATION_ACTION";
    public static boolean couldNotReachHost = false;

    public void onCreate(Bundle icicle) {
      super.onCreate(icicle);
      logD("onCreate()");
//      Resources.Configuration config = new Resources.Configuration();
//      Settings.System.getConfiguration(getContentResolver(), config);
//      locale = config.locale;
      startSemaphore = new Semaphore(1);
      startSemaphore.acquireUninterruptibly();
      readySemaphore = new Semaphore(1);
      loadUsernameAndPassword();
      setRegisteredStatus();
//      serverLocUpdateQueue = new LinkedBlockingQueue<Location>();

      CacheManager1.printToLog(this, homeDomain);

      windowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
      android.os.IServiceManager sm = android.os.ServiceManagerNative.getDefault();
      try {
        volumeService = android.view.RingerVolumeNative.asInterface(sm.getService("volume"));
      } catch (DeadObjectException d) {
        logD("got dead object retrieveing IRingerVolume, " + d.getMessage());
      }
//      handler = new Handler() {
//        @Override
//        public void handleMessage(Message msg) {
//          switch (msg.what) {
//          case SmackService.CHATROOM_MESSAGE:
//            logD("forwarding chat message to handler");
//            if (!(msg.obj instanceof XMPPMessage))
//              super.handleMessage(msg);
//            XMPPMessage xmpp = (XMPPMessage)msg.obj;
//            queueXmppMessage(xmpp);
//            break;
//          case SmackService.PRIVATE_MESSAGE:
//            logD("forwarding private message to handler");
//            if (!(msg.obj instanceof XMPPMessage))
//              super.handleMessage(msg);
//            XMPPMessage xmpp1 = (XMPPMessage)msg.obj;
//            queueXmppMessage(xmpp1);
//            break;
//          default:
//              super.handleMessage(msg);
//          }
//        }
//      };

      eventQueue = new LinkedBlockingQueue<Event>();

      requestWindowFeature(Window.FEATURE_PROGRESS);
      setContentView(R.layout.main);

//      setProgress(100);
      setProgressBarVisibility(false);

      CacheManager.setCacheDisabled(false);
      browser = (WebView)findViewById(R.id.browser);
      CacheSyncManager.createInstance(this);

      jsiCall = new JSICall(this);
      jsiSms = new JSISms(this);
      jsiEmail = new JSIEmail(this);
      jsiZoom = new JSIZoom(this);
      jsiSensor = new JSISensor(this);
      jsiUser = new JSIUser(this);
      jsiCache = new JSICache(this);
      jsiView = new JSIView(this);
      jsiEventManager = new JSIEventManager(this);
      jsiXmpp = new JSIXmpp(this);
      jsiXmppEvent = new JSIXmppEvent(this);
      jsiNotification = new JSINotification(this);
      jsiHist = new JSIBrowserHistory(this);
      jsiHistEvent = new JSIBrowserHistoryEvent(this);
      jsiKeyboard = new JSIKeyBoard(this);
      jsiKeyEvent = new JSIKeyEvent(this);
      jsiLoc = new JSILocation(this);
      jsiLocEvent = new JSILocationEvent(this);
      jsiCam = new JSICamera(this);
      jsiCamEvent = new JSICameraEvent(this);
      jsiLog = new JSILog(this);

      browser.getSettings().setJavaScriptEnabled(true);
      browser.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
      browser.getSettings().setUseDoubleTree(false);
      browser.addJavascriptInterface(jsiCall, "jsiCall");
      browser.addJavascriptInterface(jsiSms, "jsiSms");
      browser.addJavascriptInterface(jsiEmail, "jsiEmail");
      browser.addJavascriptInterface(jsiZoom, "jsiZoom");
      browser.addJavascriptInterface(jsiSensor, "jsiSensor");
      browser.addJavascriptInterface(jsiCache, "jsiCache");
      browser.addJavascriptInterface(jsiUser, "jsiUser");
      browser.addJavascriptInterface(jsiView, "jsiView");
      browser.addJavascriptInterface(jsiXmpp, "jsiXmpp");
      browser.addJavascriptInterface(jsiXmppEvent, "jsiXmppEvent");
      browser.addJavascriptInterface(jsiNotification, "jsiNotification");
      browser.addJavascriptInterface(jsiHist, "jsiBrowserHistory");
      browser.addJavascriptInterface(jsiHistEvent, "jsiBrowserHistoryEvent");
      browser.addJavascriptInterface(jsiKeyboard, "jsiKeyboard");
      browser.addJavascriptInterface(jsiKeyEvent, "jsiKeyEvent");
      browser.addJavascriptInterface(jsiLoc, "jsiGeoLocation");
      browser.addJavascriptInterface(jsiLocEvent, "jsiGeoLocationEvent");
      browser.addJavascriptInterface(jsiCam, "jsiCamera");
      browser.addJavascriptInterface(jsiCamEvent, "jsiCameraEvent");
      browser.addJavascriptInterface(jsiLog, "jsiLog");
      browser.addJavascriptInterface(jsiEventManager, "jsiEventManager");
      browser.getSettings().setSavePassword(false); // allows password input fields
      browser.setWebChromeClient(new WebChromeClient() {
        @Override
        public void onProgressChanged(WebView wv, int newProgress) {
          logD("progress changed: " + newProgress);

          if (couldNotReachHost && newProgress != 100) {
            couldNotReachHost = false;
          }
//          if (!useBuiltInProgress)
//            return;

          if (newProgress == 0 || newProgress == 100) {
            setProgressBarVisibility(false);
            return;
          }

          int progress = 100 * newProgress;
          setProgress(progress);
          setProgressBarVisibility(true);
        }

        @Override
        public void onReceivedTitle(WebView wv, String title) {
          logD("received title: " + title);
          if (!getApplicationTitle().equals(title))
            setApplicationTitle(title);
        }
      });
      browser.setWebViewClient(new WebViewClient() {
        @Override
        public void onUnhandledKeyEvent(WebView wv, KeyEvent event) {
          logD("onUnhandledKeyEvent");
          logD("pressed key: " + event.getKeyCode());
        }

        @Override
        public void onReceivedError(WebView wv, int errorCode, String description) {
          logD("onReceivedError: " + errorCode + ", " + description);
          if (isSubscribed(Event.Type.network)) {
            JSINetworkEvent event = new JSINetworkEvent(wv.getContext());
            event.errorCode = errorCode;
            event.message = description;
            queueEvent(event);
          }
          if (errorCode == -6)
            couldNotReachHost = true;
        }
        @Override
        public void onTooManyRedirects(WebView wv, android.os.Message cancelMsg, android.os.Message continueMsg) {
          logD("onTooManyRedirects, cancelMessage: " + cancelMsg + ", continueMessage: " + continueMsg);
        }
//        @Override
//        public boolean shouldOverrideUrlLoading(WebView wv, String url) {
//          Bhoost.currentUrl = url;
//          return true;
//        }

        @Override
        public boolean shouldOverrideKeyEvent(WebView wv, KeyEvent event) {
          return false;
        }
      });

      CookieManager cookieManager = CookieManager.getInstance();
      cookieManager.setAcceptCookie(true);
      cookieSyncManager = CookieSyncManager.createInstance(browser.getContext());
      cookieSyncManager.startSync();

      boolean hasCookies = cookieManager.hasCookies();

      String url = null;
      try {
        if (icicle != null)
          url = icicle.getString(Bhoost.urlKey);
      } catch (Throwable e) {
        logD("failed to retrieve url from bundle: " + e.getMessage());
      }
      if (url != null) {
        Bhoost.currentUrl = url;
      }

      String u;
//      if (jsiUser.getPassword() == null) {
//        u = homeUrl + "/j_security_activation?j_deviceId=" + jsiUser.getDeviceId();
//        u = homeUrl + "/register/new-mobile-user.html";
//      }
//      else {
        u = Bhoost.currentUrl;
//      }
/*
      if (u.contains("?"))
        u += "&X-Ajax=y";
      else
        u += "?X-Ajax=y";
 */
      logD("loading url: " + u);
      browser.loadUrl(u);
//      browser.loadUrl(homeUrl);
//      browser.loadUrl("http://web.mit.edu/~hungry/Public/test1.html");
      setContentView(browser);
      nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
	}

	private void setRegisteredStatus() {
	  if (isRegistered())
	    registered = true;
	  else
	    registered = false;
  }

  public boolean isRegistered() {
    File username = getFileStreamPath("username");
    if (username.exists())
      return true;
    return false;
	}

	private void loadUsernameAndPassword() {
    String imei = android.os.SystemProperties.get(android.telephony.TelephonyProperties.PROPERTY_IMEI);
    FileInputStream in = null;
    String iccid = android.os.SystemProperties.get(android.telephony.TelephonyProperties.PROPERTY_ICCID);
    String imsi = android.os.SystemProperties.get(android.telephony.TelephonyProperties.PROPERTY_IMSI);
    Bhoost.deviceId = imei + iccid + imsi; // + new Date().getTime();
    Bhoost.logD(Bhoost.deviceId);
    File username = getFileStreamPath("username");
    // HACK
//    if (username.exists())
//      username.delete();
    // end HACK
	  if (!username.exists())
	    return;

	  BufferedReader reader = null;
	  try {
	    in = openFileInput("username");
	    reader = new BufferedReader(new InputStreamReader(in), 8192);
	    String line = null;
	    String name = null;
	    while ((line = reader.readLine()) != null) {
        if (name == null)
          name = line;
        else
	        password = line;
	    }
	    Bhoost.username = name;
      Bhoost.password = password;
	  } catch (FileNotFoundException f) {
	    logD("couldn't get input stream to file: username");
	  } catch (IOException i) {
	    logD("couldn't read from username file");
	  } finally {
	    try {
        if (reader != null)
          reader.close();
  	    if (in != null)
  	      in.close();
	    } catch (IOException i) {}
	  }

  }

	private void writeUsernameAndPassword(String name, String pwd) {
    File username = getFileStreamPath("username");
    if (username.exists())
      username.delete();

    FileOutputStream out = null;
    BufferedWriter writer = null;
    try {
      out = openFileOutput("username", MODE_PRIVATE);
      writer = new BufferedWriter(new OutputStreamWriter(out));
      PrintWriter pw = new PrintWriter(writer);
      pw.println(name);
      pw.println(pwd);
    } catch (FileNotFoundException f) {
      logD("couldn't get output stream to file: username");
    } catch (IOException i) {
      logD("couldn't write to username file");
    } finally {
      try {
        if (writer != null)
          writer.close();
        if (out != null)
          out.close();
      } catch (IOException i) {}
    }
	}

	public void startReceivingXMPPMessages() {
	  if (chatroomMsgReceiver == null) {
      chatroomMsgReceiver = new IntentReceiver() {
        @Override
        public void onReceiveIntent(Context context, Intent intent) {
          logD("received chatroom message");
          Bundle stuff = intent.getExtras();
          Object obj = stuff.getParcelable("message");
          if (obj instanceof XMPPMessage)
            queueXmppMessage((XMPPMessage)obj);
        }
      };
      registerReceiver(chatroomMsgReceiver, new IntentFilter(SmackService.CHATROOM_MESSAGE_ACTION));
	  }

	  if (privateMsgReceiver == null) {
      privateMsgReceiver = new IntentReceiver() {
        @Override
        public void onReceiveIntent(Context context, Intent intent) {
          Bundle stuff = intent.getExtras();
          Object obj = stuff.getParcelable("message");
          XMPPMessage xmppMsg;
          if (obj instanceof XMPPMessage)
            xmppMsg = (XMPPMessage)obj;
          else
            return;

          if (xmppMsg.sender == null)
            return;

          logD("received private message from " + xmppMsg.sender);

          if (!xmppMsg.sender.toLowerCase().equals("cachemanager@conference.lablz.com/marco-desktop")) {
            queueXmppMessage((XMPPMessage)obj);
            return;
          }

          cacheFile(xmppMsg.url, xmppMsg.body);
        }
      };
      registerReceiver(privateMsgReceiver, new IntentFilter(SmackService.PRIVATE_MESSAGE_ACTION));
	  }
	}

	public void stopReceivingXMPPMessages() {
	  if (chatroomMsgReceiver != null) {
	    try {
	      unregisterReceiver(chatroomMsgReceiver);
	    } catch (Exception e) {
	      logD("couldn't unreg chatroom msg receiver: " + e.getMessage());
	    }
	    chatroomMsgReceiver = null;
	  }
    if (privateMsgReceiver != null) {
      try {
        unregisterReceiver(privateMsgReceiver);
      } catch (Exception e) {
        logD("couldn't unreg private msg receiver: " + e.getMessage());
      }
      privateMsgReceiver = null;
    }

    if (eventQueueThread != null)
      eventQueueThread.mustStop();
	}

	public void startEventQueueThread() {
	  if (jsiEventManager.isEventsViaTimer())
	    return;

    if (eventQueueThread != null)
      return;

    logD("starting event queue thread");

    eventQueueThread = new EventQueueThread();
    eventQueueThread.start();
	}

	class EventQueueThread extends Thread {
    private boolean handleEvents = true;

    public void mustStop() {
      handleEvents = false;
    }

    public void finish() {
      startSemaphore.release();
      readySemaphore.release();
      eventQueue.clear();
      eventQueue = null;
    }

    public void run() {
      startSemaphore.acquireUninterruptibly();

	    while (handleEvents) {
	      if (windowManager == null)
	        windowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));

	      Event event = null;
	      try {
          Bhoost.logD("waiting for pop, queue empty: " + eventQueue.isEmpty());
	        event = eventQueue.take();
          Bhoost.logD("popped");
        } catch (InterruptedException i) {
          Bhoost.logD("event queue thread interrupted: " + i.getMessage());
          if (!handleEvents)
            return;
          else
            continue;
        }

        if (!handleEvents) {
          logD("stopping event queue thread");
          return;
        }

        copyEvent(event);

        //logD("simulating key event");
        readySemaphore.acquireUninterruptibly();
        simulateKeystroke(injectKeyCode);
	    }

	    finish();
	  }
	}

	private boolean copyEvent(Event event) {
    logD("copying event of type " + event.eventType);
    switch (event.eventType) {
      case xmpp:
        jsiXmppEvent.copyFrom(event);
        return true;
      case browserHistory:
        jsiHistEvent.copyFrom(event);
        return true;
      case key:
        jsiKeyEvent.copyFrom(event);
        return true;
      case geoLocation:
        jsiLocEvent.copyFrom(event);
        return true;
      case camera:
        jsiCamEvent.copyFrom(event);
        return true;
      default:
        return false;
    }
	}

//	public void startSendingLocUpdates() {
//	  sendServerUpdates = true;
//	  if (serverUpdateThread != null) {
//	    logD("server update thread already exists");
//	    return;
//	  }
//
//	  logD("starting server update thread");
//
//    serverUpdateThread = new ServerUpdateThread();
//    serverUpdateThread.start();
//
////    if (locationManager != null) {
////      Location loc = locationManager.getCurrentLocation("gps");
////      queueServerUpdate(loc); // send initial location
////    }
//	}

//	class ServerUpdateThread extends Thread {
//	  public void run() {
//	    while (sendServerUpdates) {
//	      Location loc = null;
//        try {
//          loc = serverLocUpdateQueue.take();
//          while (!serverLocUpdateQueue.isEmpty()) { // only need to send latest
//            loc = serverLocUpdateQueue.take();
//          }
//        } catch (InterruptedException i) {
//          Bhoost.logD("server update thread interrupted: " + i.getMessage());
//          if (!sendServerUpdates)
//            return;
//          else
//            continue;
//        }
//
//        if (loc == null)
//          continue;
//
//        if (!sendServerUpdates) {
//	        logD("exiting server update thread");
//	        return;
//	      }
//
//        String url = "";
//        InputStream is = null;
//        HttpURLConnection conn = null;
//        try{
//          StringBuilder urlBuilder = new StringBuilder(100);
//          urlBuilder.append(homeUrl);
//          urlBuilder.append("/location?latitude=");
//          urlBuilder.append(Double.toString(loc.getLatitude()));
//          urlBuilder.append("&longitude=");
//          urlBuilder.append(Double.toString(loc.getLongitude()));
//          url = urlBuilder.toString();
//          logD("sending location update to URL: " + url);
//          URL connectUrl = new URL(url);
//          conn = (HttpURLConnection) connectUrl.openConnection();
//          conn.setUseCaches(false);
//          conn.setDoOutput(true);
//          conn.setRequestMethod("GET");
//          conn.setRequestProperty("Host", connectUrl.getHost());
//          is = conn.getInputStream();
//        } catch (MalformedURLException m) {
//          logD("malformed url in sendServerUpdate: " + url);
//        } catch (IOException i) {
//          logD("IOException in sendServerUpdate: " + i.getMessage());
//        } finally {
//          try {
//            if (is != null) {
//              logD("closing stream");
//              is.close();
//            }
//            if (conn != null) {
//              logD("disconnecting HttpURLConnection");
//              conn.disconnect();
//            }
//          } catch (IOException ii) {}
//        }
//	    }
//    }
//	}

//	class CachingThread extends Thread {
//	  public void run() {
//	    while (cacheFiles) {
//
//	    }
//	  }
//	}
//
//	private void startCaching() {
//	  cacheFiles = true;
//	  if (cachingThread != null)
//	    return;
//
//	  logD("starting caching thread");
//	  cachingThread = new CachingThread();
//	  cachingThread.start();
//	}
//
//	private void stopCaching() {
//	  cacheFiles = false;
//	}

	/**
	 * send location updates no more often than once/5min, and only if position has changed by minDistance compared to last update
	 */
  class MyIntentReceiver extends IntentReceiver {

    public MyIntentReceiver() {}

    @Override
    public void onReceiveIntent(Context context, Intent locIntent) {
      //if (context != null) {this.abortBroadcast(); return;}
      Location loc = (Location) locIntent.getExtra("location");
      if (previousRecordedLoc == null) // this only happens first time through
        queueLocationEvent(loc);
      if (loc != null && previousRecordedLoc != null && loc.distanceTo(previousRecordedLoc) >= minDistance) {
        logD("new location: " + loc.getLatitude() + ","  + loc.getLongitude() + ", distance traveled: " + loc.distanceTo(previousRecordedLoc));
        queueLocationEvent(previousRecordedLoc);
      }
      previousRecordedLoc = loc != null ? loc : previousRecordedLoc;
      this.abortBroadcast();

//      long currentTime = System.currentTimeMillis();
//      if (previousSentLoc != null) {
//        distanceTraveledSinceLastUpdate = loc.distanceTo(previousSentLoc);
//        logD("distance travelled: " + distanceTraveledSinceLastUpdate);
//        timeSinceLastUpdate = currentTime - timeAtLastUpdate;
//        logD("time passed: " + timeSinceLastUpdate);
//      }
//      else {
//        distanceTraveledSinceLastUpdate = minDistance + 1;
//        timeSinceLastUpdate = TIME_TO_SERVER_LOCATION_UPDATE + 1;
//      }
//
//      if (distanceTraveledSinceLastUpdate > minDistance && timeSinceLastUpdate > TIME_TO_SERVER_LOCATION_UPDATE) {
//        logD("Sending loc update to server. Since last send, time passed = " + timeSinceLastUpdate + ", distance traveled = " + distanceTraveledSinceLastUpdate);
//        previousSentLoc = loc;
//        timeAtLastUpdate = currentTime;
//        timeSinceLastUpdate = 0;
//        distanceTraveledSinceLastUpdate = 0;
//        queueServerUpdate(loc);
//      }
    }
  }

  public void queueEvent(Event event) {
    Event.Type type = event.getType();
    if (isSubscribed(type)) {
      logD("queueing event of type: " + event.getType().toString());
      eventQueue.add(event);
    }
//    simulateKeystroke(injectKeyCode);
  }

  public void queueLocationEvent(Location loc) {
    if (loc == null)
      return;

//    logD("queue location event");
    JSILocationEvent locEvent = new JSILocationEvent(this);
    locEvent.latitude = loc.getLatitude();
    locEvent.longitude = loc.getLongitude();
    queueEvent(locEvent);
  }

//  public void queueServerUpdate(Location loc) {
//    // send location update via HTTP GET request
//    logD("queue location update to server");
//    serverLocUpdateQueue.add(loc);
//  }

  private void queueXmppMessage(XMPPMessage xmpp1) {
    logD("got message in Bhoost: " + xmpp1.body);
    JSIXmppEvent xmppEvent = new JSIXmppEvent(this);
    xmppEvent.message = xmpp1;
    queueEvent(xmppEvent);
  }

  private void startReceivingLocUpdates() {
    logD("registering for loc updates");
    if (locationManager == null) {
      logD("Location Manager is NULL!");
      locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    }

    LocationProvider provider = null;
    try {
      provider = locationManager.getProvider("gps");
    } catch (Exception e) {
      logD("internal android exception in getProvider");
    }
    if (provider == null) {
      return;
    }

    IntentFilter locationIntentFilter = new IntentFilter(LOCATION_UPDATE);
    Intent locUpdateIntent = new Intent(LOCATION_UPDATE);
    if (locationIntentReceiver != null) {
      unregisterReceiver(locationIntentReceiver);
      locationIntentReceiver = null;
    }
    locationIntentReceiver = new MyIntentReceiver();
    Intent regIntent = registerReceiver(locationIntentReceiver, locationIntentFilter);
//    if (regIntent != null)
//      previousSentLoc = (Location) regIntent.getExtra("location");
    locationManager.requestUpdates(provider, minTime, minDistance, locUpdateIntent);
    Location loc = locationManager.getCurrentLocation("gps");
    previousRecordedLoc = loc;
    queueLocationEvent(loc);
  }

//  private ISmackServiceCallback mCallback = new ISmackServiceCallback.Stub() {
//    /**
//     * This is called by the remote service regularly to tell us about
//     * new values.  Note that IPC calls are dispatched through a thread
//     * pool running in each process, so the code executing here will
//     * NOT be running in our main thread like most other things -- so,
//     * to update the UI, we need to use a Handler to hop over there.
//     */
//    public void receivedMessage(XMPPMessage msg) {
//      android.os.Message message = android.os.Message.obtain();
//      message.obj = msg;
//      handler.sendMessage(message);
//    }
//  };

  class SmackServiceConnection implements ServiceConnection
  {
      public void onServiceConnected(ComponentName className, IBinder boundService)
      {
        Bhoost.logD("service connected");
        service = ISmackService.Stub.asInterface((IBinder)boundService);
        if (service != null) {
          try {
            service.clientPaused(false);
          } catch (DeadObjectException d) {
            logD("dead object in onServiceConnected: " + d.getMessage());
          }
        }
//        try {
//          service.registerCallback(mCallback);
//        } catch (DeadObjectException e) {
//            // In this case the service has crashed before we could even
//            // do anything with it; we can count on soon being
//            // disconnected (and then reconnected if it can be restarted)
//            // so there is no need to do anything here.
//        }
      }

      public void onServiceDisconnected(ComponentName className)
      {
        Bhoost.logD("service disconnected");
        service = null;
      }
  };


  private void initService() {
    if (service != null)
      return;

    serviceConnection = new SmackServiceConnection();
    Intent i = new Intent();
    i.setClass(this, SmackService.class);
    bindService(i, serviceConnection, Context.BIND_AUTO_CREATE);
  }

  private void releaseService() {
    if (serviceConnection == null) {
      logD("unbound service!");
      return;
    }
//    if (service != null) {
//      try {
//        service.unregisterCallback(mCallback);
//      } catch (DeadObjectException e) {
//          // There is nothing special we need to do if the service
//          // has crashed.
//     }
//  }
    unbindService(serviceConnection);
    serviceConnection = null;
  }

	public void setApplicationTitle(String title) {
	  setTitle(title);
	}

	public String getApplicationTitle() {
	  return getTitle().toString();
	}

  @Override
  public void onDestroy() {
    logD("onDestroy()");
    super.onDestroy();
    if (serviceConnection != null)
      releaseService();

//    stopSendingLocationUpdates();
    if (jsiEventManager != null && jsiEventManager.isEventsViaTimer())
      stopEventQueueThread();
  }

  private void stopEventQueueThread() {
    if (jsiEventManager.isEventsViaTimer())
      return;

    logD("stopping event queue thread");
    handleEvents = false;
    if (jsiLocEvent != null)
      queueEvent(jsiLocEvent);
  }

//  private void stopSendingLocationUpdates() {
//    sendServerUpdates = false;
//    if (previousSentLoc != null)
//      serverLocUpdateQueue.add(previousSentLoc);
//  }

  private void stopReceivingLocationUpdates() {
    if (locationIntentReceiver != null) {
      try {
        unregisterReceiver(locationIntentReceiver);
      } catch (Exception e) {
        logD("couldn't unregister loc receiver: " + e.getMessage());
      }
      locationIntentReceiver = null;
    }
    if (locationManager != null)
      locationManager.removeUpdates(new Intent(LOCATION_UPDATE));
  }

  @Override
  public void onPause() {
    super.onPause();
    if (isSubscribed(Event.Type.geoLocation))
      stopReceivingLocationUpdates();
    if (isSubscribed(Event.Type.xmpp))
      stopReceivingXMPPMessages();

    try {
      if (service != null)
        service.clientPaused(true);
    } catch (DeadObjectException d) {
      logD("got dead object on service.saveMessages: " + d.getMessage());
    }

    logD("onPause()");
  }

  @Override
  public void onStart() {
    super.onStart();
    logD("onStart()");
//    try {
//      if (service != null)
//        service.cancelNotification();
//    } catch (DeadObjectException d) {
//      logD("couldn't cancel notification");
//    }
//    if (nm == null)
//      nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
//    nm.cancel(R.string.notification);
  }

  @Override
  public void onRestart() {
    super.onRestart();
    logD("onRestart()");
  }

  @Override
  public void onStop() {
    super.onStop();
    CacheSyncManager.getInstance().stopSync();
    logD("onStop()");
  }

  @Override
  public void onFreeze(Bundle outState) {
    super.onFreeze(outState);
    logD("onFreeze()");

    outState.putString(Bhoost.urlKey, Bhoost.currentUrl);
  }

  @Override
  public void onResume() {
    super.onResume();
    logD("onResume() entered");
    CacheSyncManager.getInstance().startSync();
    handleEvents = true;
    try {
      logD("starting service");
      initService();
      logD("started service");
    } catch (Throwable e) {
      logD("failed in onCreate: " + e.getMessage());
    }

    if (isSubscribed(Event.Type.geoLocation)) {
      startReceivingLocUpdates();
//      startSendingLocUpdates();
    }
    if (isSubscribed(Event.Type.xmpp))
      startReceivingXMPPMessages();

    if (jsiEventManager != null && jsiEventManager.isEventsViaTimer())
      startEventQueueThread();

    try {
      if (service != null)
        service.clientPaused(false);
    } catch (DeadObjectException d) {
      logD("got dead object on service.clientPaused : " + d.getMessage());
    }

    try {
      if (nm != null)
        nm.cancel(R.string.notification);
    } catch (Exception e) {
      logD("got exception while canceling notification: " + e.getMessage());
    }

    logD("onResume() done");
  }

  private void simulateKeystroke(final int keyCode) {
//    runOnUIThread(new Runnable() {
    Runnable r = new Runnable() {
      public void run() {
        doInjectKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
        doInjectKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
      }
    };
    Thread t = new Thread(r);
    t.start();
  }

  /** This function actually handles the KeyStroke-Injection. **/
  private void doInjectKeyEvent(KeyEvent kEvent) {
    try {
//      if (browser != null) {
//        browser.dispatchKeyEvent(kEvent);
//      }
      /* Inject the KeyEvent to the Window-Manager. */
      if (windowManager != null) {
        logD("dispatching key event: " + kEvent.getKeyCode() + ", with action: " + kEvent.getAction());
        windowManager.injectKeyEvent(kEvent.getAction() == KeyEvent.ACTION_DOWN, kEvent.getKeyCode(),
                 kEvent.getRepeatCount(), kEvent.getDownTime(), kEvent.getEventTime(), true);
      }
    } catch (DeadObjectException e) {
      logD("failed to inject key event: " + e.getMessage());
    }
  }

  public boolean isSubscribed(Event.Type type) {
    return jsiEventManager != null && jsiEventManager.isSubscribed(type);
  }

  @Override
  public boolean onKeyDown(int keyCode, KeyEvent event) {
    logD("event.getAction() = " + event.getAction());

    if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
      return false;
//    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
//      jsiCam.takePicture("hello");
//    }
//    if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
//      logD("syncing");
//      jsiCache.put(null, 0, null);
//      CacheManager1.printToLog(this, homeDomain);
//      CacheSyncManager.getInstance().sync();
//    }
//    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
//      jsiCam.takePicture("hello");
//      logD("accel: " + jsiAccel.available());
//      logD("compass: " + jsiCompass.available());
//      logD("orient: " + jsiOrient.available());
//      jsiSensor.enableSensor("jdsaijdlkas");
//      jsiSensor.available("jdsaijdlkas");
//      jsiSensor.read("jdsaijdlkas");
//      jsiSensor.getUpdateRate("jdsaijdlkas");
//      jsiSensor.setUpdateRate("jdsaijdlkas", 102);
//      jsiSensor.unsetUpdateRate("jdsaijdlkas");
//      jsiSensor.enableSensor(Sensors.SENSOR_ACCELEROMETER);
//      jsiSensor.enableSensor(Sensors.SENSOR_COMPASS);
//      jsiSensor.enableSensor(Sensors.SENSOR_ORIENTATION);
//      float[] acc = jsiSensor.read(Sensors.SENSOR_ACCELEROMETER);
//      String accel = "";
//      if (acc != null) {
//        for (int i = 0; i < acc.length; i++) {
//          accel += acc[i] + " ";
//        }
//      }
//      logD("accel vals: " + accel);
//      float[] com = jsiSensor.read(Sensors.SENSOR_COMPASS);
//      String compass = "";
//      for (int i = 0; i < com.length; i++) {
//        compass += com[i] + " ";
//      }
//      logD("compass vals: " + compass);
//      float[] ori = jsiSensor.read(Sensors.SENSOR_ORIENTATION);
//      String orient = "";
//      for (int i = 0; i < ori.length; i++) {
//        orient += ori[i] + " ";
//      }
//      logD("orient vals: " + orient);
//    }
//    else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
//      jsiNotification.cancelNotification();
//    }

    if (isSubscribed(Event.Type.key)) {
      JSIKeyEvent keyEvent = new JSIKeyEvent(this);
      keyEvent.keyCode = keyCode;
      keyEvent.action = event.getAction();
      queueEvent(keyEvent);
    }

//    if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
//      if (!browser.getUrl().toLowerCase().contains(homeUrl.toLowerCase())) {
//        return super.onKeyDown(keyCode, event);
//      }
//      return true;
//    }
    if (keyCode == KeyEvent.KEYCODE_SOFT_LEFT) {
      if (couldNotReachHost) {
        logD("failed to load page, showing native menu");
        return super.onKeyDown(keyCode, event);
      }

      String url = browser.getUrl();
      if (url == null || !url.toLowerCase().contains(homeUrl.toLowerCase())) {
        return super.onKeyDown(keyCode, event);
      }
      return true;
    }
    else if (keyCode == KeyEvent.KEYCODE_BACK) {
      String url = browser.getUrl();
      if (url == null || !url.toLowerCase().contains(homeUrl.toLowerCase())) {
        goBack();
      }
      return true;
    }
//    switch (keyCode) {
//      case KeyEvent.KEYCODE_BACK:
//        goBack();
//        return true;
//      case KeyEvent.KEYCODE_SOFT_LEFT:
////        showMenu();
//        return super.onKeyDown(keyCode, event);
//      case KeyEvent.KEYCODE_SOFT_RIGHT:
////        showMenu();
//        return super.onKeyDown(keyCode, event);
//    }
//
    return super.onKeyDown(keyCode, event);
  }

  public void goBack() {
    if (browser.canGoBack()) browser.goBack();
  }

  public void goForward() {
    if (browser.canGoForward()) browser.goForward();
  }

  public void reload() {
    browser.reload();
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
      super.onCreateOptionsMenu(menu);
      menu.add(0, Bhoost.BACK_MENU_ITEM, "Back");
      menu.add(0, Bhoost.REFRESH_MENU_ITEM, "Refresh");
      menu.add(0, Bhoost.FORWARD_MENU_ITEM, "Forward");
      menu.add(0, Bhoost.HOME_MENU_ITEM, "Home");
      menu.add(0, Bhoost.STOP_MENU_ITEM, "Stop");
      menu.add(0, Bhoost.EXIT_MENU_ITEM, "Exit");
//      menu.add(0, Bhoost.CAMERA_MENU_ITEM, "Camera");
      return true;
  }

//  public void hideTitle() {
//    super.setTitle("");
//    titleHidden = true;
//  }
//
//  public void setTitle(String newTitle) {
//    super.setTitle(newTitle);
//    titleHidden = false;
//  }
//

  @Override
  public boolean onOptionsItemSelected(Menu.Item item){
      logD("menu item selected");
      switch (item.getId()) {
      case Bhoost.HOME_MENU_ITEM:
        browser.loadUrl(homeUrl);
        return true;
      case Bhoost.REFRESH_MENU_ITEM:
        reload();
        return true;
      case Bhoost.BACK_MENU_ITEM:
        goBack();
        return true;
      case Bhoost.FORWARD_MENU_ITEM:
        goForward();
        return true;
      case Bhoost.STOP_MENU_ITEM:
        browser.stopLoading();
        return true;
      case Bhoost.EXIT_MENU_ITEM:
        jsiView.exit();
        return true;
//      case Bhoost.CAMERA_MENU_ITEM:
//        jsiCam.takePicture("picture1.jpeg");
//        return true;
      }
      return false;
  }

	public static void logE(String msg) {
	  Log.e(Bhoost.TAG, msg);
	}

  public static void logD(String msg) {
    Log.d(Bhoost.TAG, msg);
  }

  public void startCameraActivity(String path) {
    logD("start cam activity");
    Intent camIntent = new Intent(this, CameraPreview.class);
    camIntent.putExtra("writepath", path);
    startSubActivity(camIntent, CAMERA_ACTIVITY);
  }

  protected void onActivityResult(int requestCode, int resultCode, String data, Bundle extras) {
    if (requestCode == CAMERA_ACTIVITY) {
      logD("took picture: " + (resultCode == RESULT_OK));
      if (!isSubscribed(Event.Type.camera))
        return;

      JSICameraEvent camEvent = new JSICameraEvent(this);
      if (resultCode == RESULT_OK) {
        File file = new File(jsiCam.path);
        if (file == null)
          return;

        long length = file.length();
        String uploadUrl = upload(jsiCam.path, jsiCam.uploadUrl, "image/jpeg", length);
        if (uploadUrl == null)
          return;

        camEvent.url = uploadUrl;
        camEvent.length = length;
        queueEvent(camEvent);
      }
    }
  }

  /**
   * splits url into host, directory path. For example: http://marco.lablz.com/movies/example.js would return {marco.lablz.com, movies, example.js}
   * @param url
   * @return
   */
//  public String[] splitUrl(String url) {
//    String http = "http://";
//    int httpIndex = url.indexOf(http);
//    if (httpIndex == -1)
//      return null;
//
//    int slashIndex = url.indexOf("/", httpIndex);
//    if (slashIndex == -1 || slashIndex < httpIndex)
//      return null;
//
//    String host = url.substring(httpIndex + http.length(), slashIndex).trim();
//    if (host.length() != 0)
//      return host;
//
//    return null;
//  }

  ////////////////////////////////////////////////

//  public void cacheFile(String url, String data, long expires, String lastModified) {
//    if (jsiCache != null)
//      jsiCache.put(url, data, expires, lastModified);
//  }

  public void cacheFile(String url, String method) {
    if (method == null)
      return;

    String lowerCase = method.toLowerCase().trim();
    if (lowerCase.equals("put") && url != null)
      cachePut(url);
//    else if (lowerCase.equals("delete") && url != null)
//      cacheDelete(url);
    else if (lowerCase.equals("clear"))
      cacheClear();
  }

  private void cachePut(String url) {
    CacheManager1.put(this, url);
  }

//  private void cacheDelete(String url) {
//    CacheManager1.delete(this, url);
//  }

  private void cacheClear() {
    CacheManager1.clearCache(this);
  }

  ///////////////////////////////////////////////

  class JSISms {
    Context con;

    public JSISms(Context con) {
      this.con = con;
    }

    public void sendSms(final String number, final String text) {
      runOnUIThread(new Runnable() {
        public void run() {
          SmsManager sm = SmsManager.getDefault();
          sm.sendTextMessage(number, null, text, null, null, null);
        }
      });
    }
  }

  class JSIEmail {
    Context con;

    public JSIEmail(Context con) {
      this.con = con;
    }
  }

  class JSICall {
    Context con;

    public JSICall(Context con) {
      this.con = con;
    }

    public void dial(final String number) {
      logD("calling " + number);
      runOnUIThread(new Runnable() {
        public void run() {
          IServiceManager sm = ServiceManagerNative.getDefault();
          IPhone phoneService = null;
          try {
            phoneService = IPhone.Stub.asInterface(sm.getService("phone"));
            phoneService.dial(number);
          } catch (DeadObjectException d) {
            logD("couldn't dial phone call: " + d.getMessage());
          }
        }
      });
    }

    public void call(final String number) {
      runOnUIThread(new Runnable() {
        public void run() {
          IServiceManager sm = ServiceManagerNative.getDefault();
          IPhone phoneService = null;
          try {
            phoneService = IPhone.Stub.asInterface(sm.getService("phone"));
            phoneService.call(number);
          } catch (DeadObjectException d) {
            logD("couldn't make phone call: " + d.getMessage());
          }
        }
      });
    }
  }

  class JSINetworkEvent extends Event {
    Context con;
    int errorCode;
    String message;

    public JSINetworkEvent(Context con) {
      this.con = con;
    }

    public void copyFrom(Event event) {
      if (!(event instanceof JSINetworkEvent))
        throw new IllegalArgumentException("this is not a jsinetworkevent!");

      JSINetworkEvent j = (JSINetworkEvent)event;
      errorCode = j.errorCode;
      message = j.message;
    }
  }

  class JSIZoom {
    Context con;

    public JSIZoom(Context con) {
      this.con = con;
    }

    public int getScreenWidth() {
      DisplayMetrics dm = new DisplayMetrics();
      getWindowManager().getDefaultDisplay().getMetrics(dm);
      int screenWidth = dm.widthPixels;
      return screenWidth;
    }

    public int getZoomWidth() {
      if (browser != null)
        return browser.getZoomWidth();
      return -1;
    }

    public void setZoomWidth(final int width) {
      runOnUIThread(new Runnable() {
        public void run() {
          if (browser != null) {
            browser.setZoomWidth(width);
          }
        }
      });
    }
  }

  class JSICache {
    Context con;

    public JSICache(Context con) {
      this.con = con;
    }

    public void clearCache() {
      android.webkit.CacheManager1.clearCache(con);
    }

    public String[] getCache() {
      return android.webkit.CacheManager1.getCache(con, homeUrl);
    }

    public byte[] get(String url) {
      byte[] data = android.webkit.CacheManager1.get(con, url);
      logD("data from cache is null?: " + (data == null));
      return data;
    }

    /**
     * loads data from url and puts to WebViewDatabase and file system.
     * @param url
     */
    public void put(String url) {
      android.webkit.CacheManager1.put(con, url);
      cookieSync();
    }
    //
    public void cookieSync() {
      cookieSyncManager.sync();
    }

    public void put(String url, String data) {
      put(url, data.getBytes());
    }

    /**
     * this will only store to file system, not to database, so it can only be used through jsiCache.get(), it will not be automatically loaded
     * by CacheManager
     * @param url
     * @param data
     */
    public void put(String url, byte[] data) {
      android.webkit.CacheManager1.put(con, url, data);
    }

    /**
     * puts to WebViewDatabase as well as file system
     * @param url
     * @param data
     * @param expires
     */
    public void put(String url, byte[] data, long expires) {
      android.webkit.CacheManager1.put(con, url, data, expires);
    }

//    public void delete(String url) {
//      android.webkit.CacheManager1.delete(con, url);
//    }
  }

  class JSIUser {
    Context con;

    public JSIUser(Context con) {
      this.con = con;
    }
/*
    public void reactivate(String activationCode) {
      StringBuilder sb = new StringBuilder();
      sb.append(homeUrl);
      sb.append("/j_security_activation?j_activation_code=");
      sb.append(activationCode);
      sb.append("&j_username=");
      sb.append(getUserId());
      loadUrl(sb.toString());
    }


    public boolean signup(String username) {
      StringBuilder sb = new StringBuilder();
      sb.append(homeUrl);
      sb.append("/j_register?j_username=");
      sb.append(username);
      setUserId(username);
      sb.append("&-propToSet=name&name=");
      sb.append(username);
      sb.append("&j_password=");
      String pwd = genNewPassword();
      setPassword(pwd);
      sb.append(pwd);
      sb.append("&j_deviceId=");
      sb.append(getDeviceId());
      String url = sb.toString();

      final String url = sb.toString();
      Runnable r = new Runnable() { public void run() {browser.loadUrl(url);} };
      Thread t = new Thread(r);
      t.start();

//      loadUrl(url);
      return false;
    }
    private void loadUrl(String uri) {
      URL url = null;
      HttpURLConnection conn = null;
      InputStream in = null;
      try {
        url = new URL(uri);
        conn = (HttpURLConnection)url.openConnection();

        //urlConnection.setDoInput(true);
        conn.setUseCaches(false);
        conn.setRequestProperty("Host", url.getHost());
        conn.setInstanceFollowRedirects(false);
        int responseCode = -1;
        String responseMessage = null;

        conn.setRequestMethod("GET");
        try {
          in = conn.getInputStream();
        } catch (Exception e) {
          conn.disconnect();
          loadUrl(uri);
          return;
        }

        String resp = conn.getHeaderField(0);
        if (resp == null) {
          conn.disconnect();
          loadUrl(uri);
          return;
        }
        int ind = resp.indexOf(' ');
        int len = resp.length();
        while(resp.charAt(ind) == ' ')
          ind++;
        responseCode = Integer.parseInt(resp.substring(ind, ind + 3));
        ind += 3;
        while(ind < len  &&  resp.charAt(ind) == ' ')
          ind++;
        if (ind != len)
          responseMessage = resp.substring(ind);

        String username = getUserId();
        String password = getPassword();
        if (responseCode < 400)
          writeUsernameAndPassword(username, password);
        ArrayList<String> cookies = new ArrayList();
        if (responseCode == 200) {
          browser.loadUrl(homeUrl);
        }
        else if (responseCode == 302) {
          String location = null;
          for (int i=1; ; i++) {
            String s = null;
            try {
              s = conn.getHeaderFieldKey(i);
              if (s == null)
                break;
              String value = conn.getHeaderField(s);
              if (s.equalsIgnoreCase("location"))
                location = value;
              else if (s.equalsIgnoreCase("set-cookie"))
                cookies.add(value);

            } catch(Exception e) {
              len = 0;
              //setWarning(new DavWarning(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString()));
              return;
            }
          }
          location = location == null ? homeUrl : location;
          int n = cookies.size();
          if (n > 0) {
            CookieManager cm = CookieManager.getInstance();
            for (int i=0; i<n; i++)
              cm.setCookie(location, cookies.get(i));
          }
          browser.loadUrl(location);
        }
        else if (responseCode == 409  ||  responseCode == 401) {
          StringBuilder rBuf = new StringBuilder();
          rBuf.append(homeUrl);
          rBuf.append("/register/new-mobile-user.html?errMsg=");
          rBuf.append("This name is already taken. Please try another.".replace(' ', '+'));
          browser.loadUrl(rBuf.toString());
        }
      } catch (Exception e) {
        Log.e(TAG, "", e);
      } finally {
        if (in != null)
          try { in.close(); } catch (Exception e) {}
        if (conn != null)
          conn.disconnect();
//      }
      }
      //      browser.loadUrl(sb.toString());
    }
*/
    public void setUserId(String username) {
      Bhoost.username = username;
      Bhoost.password = null;
    }

    public String getUserId() {
      return Bhoost.username;
    }

    public String getDeviceId() {
      return Bhoost.deviceId;
    }

    public String getPassword() {
      if (Bhoost.password == null) {
        if (Bhoost.username != null) {
          Bhoost.password = genNewPassword();
          writeUsernameAndPassword(Bhoost.username, Bhoost.password);
        }
      }
      return Bhoost.password;
    }
    private void setPassword(String password) {
      Bhoost.password = password;
    }

    private String genNewPassword() {
      byte[] bytes = new byte[20];
      SecureRandom random = null;
      try {
        random = SecureRandom.getInstance("SHA1PRNG");
      } catch (Exception e) {
        logD(e.getMessage());
//        throw new ModuleUnavailableException(e.getMessage());
      }
      random.nextBytes(bytes);
      StringBuffer sb = new StringBuffer(20);
      for (int i = 0; i < 20; i++) {
        // look up high nibble char
        sb.append(hexChar[(bytes[i] & 0xf0) >>> 4]);
        // look up low nibble char
        sb.append(hexChar[bytes[i] & 0x0f]);
      }

      String password = sb.toString();
      Bhoost.password = password;
      return Bhoost.password;
    }

    public boolean isRegistered() {
      return Bhoost.registered;
    }
  }

  class JSIView {
    Context con;

    public JSIView(Context con) {
      this.con = con;
    }

    public void refocus() {
      runOnUIThread(new Runnable() {
        public void run() {
          if (browser != null) {
            browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN));
            browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_DOWN));
            browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN));
            browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_DOWN));
            browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP));
            browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_UP));
          }
        }
      });
    }

//    public void hideTitle() {
//
//    }

//    public void fixFocus() {
//      runOnUIThread(new Runnable() {
//        public void run() {
//          browser.clearDisappearingChildren();
//          browser.requestFocus();
//          browser.clearFocus();
//          browser.clearFormData();
//          browser.performClick();
//          browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER));
//          browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN));
//          browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP));
//          browser.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CLEAR));
//          browser.scrollBy(0, 1000);
//        }
//      });
//    }

    public void exit() {
      logD("exiting!");
      runOnUIThread(new Runnable() {
        public void run() {
          if(locationManager != null)
            locationManager.removeUpdates(new Intent(LOCATION_UPDATE));
          if (locationIntentReceiver != null)
            unregisterReceiver(locationIntentReceiver);
          if (privateMsgReceiver != null)
            unregisterReceiver(privateMsgReceiver);
          if (chatroomMsgReceiver != null)
            unregisterReceiver(chatroomMsgReceiver);

          finish();
//          System.exit(0);
        }
      });
    }

    public void setProgressIndeterminate(final boolean setIndeterminate) {
      if (useBuiltInProgress)
        useBuiltInProgress = false;

      logD("setting progress indeterminate: " + setIndeterminate);
      runOnUIThread(new Runnable() {
        public void run() {
          setProgressBarIndeterminate(setIndeterminate);
          setProgressBarVisibility(setIndeterminate);
        }
      });
    }

    public String getTitle() {
      return getApplicationTitle();
    }

    public void setProgress(int newProgress) {
      if (useBuiltInProgress)
        useBuiltInProgress = false;

      if (newProgress == 0 || newProgress == 100) {
        runOnUIThread(new Runnable() {
          public void run() {
            setProgressBarVisibility(false);
          }
        });
        return;
      }

      logD("setting progress: " + newProgress);
      final int progress = 100 * newProgress;
      runOnUIThread(new Runnable() {
        public void run() {
          setProgress(progress);
          setProgressBarVisibility(true);
        }
      });
    }

    public void setTitle(final String title) {
      logD("setting title: " + title);
      runOnUIThread(new Runnable() {
        public void run() {
          setApplicationTitle(title);
        }
      });
    }
  }

  class JSINotification {
    Context con;
    Notification notification;
    int ringerMode;

    public JSINotification(Context con) {
      this.con = con;
    }

    public void createNotification() {
      notification = new Notification();
    }

    public int getRingerMode() {
      if (volumeService == null) {
        android.os.IServiceManager sm = android.os.ServiceManagerNative.getDefault();
        try {
          volumeService = android.view.RingerVolumeNative.asInterface(sm.getService("volume"));
        } catch (DeadObjectException d) {
          logD("got dead object retrieveing IRingerVolume, " + d.getMessage());
        }
      }

      if (volumeService == null)
        return -1;
      try {
        return volumeService.getRingerMode();
      } catch (DeadObjectException d) {
        logD("got dead object getting ringer mode, " + d.getMessage());
        return -1;
      }
    }

    public void setRing() {
      notification.sound = Uri.fromFile(new File("/system/media/audio/ringtones/ringer.mp3"));
    }

    public void setVibrate(long[] vibratePattern) {
      notification.vibrate = vibratePattern;
    }

    public void setLedConfig(int ledARGB, int ledOnMS, int ledOffMs) {
      notification.ledARGB = ledARGB;
      notification.ledOnMS = ledOnMS;
      notification.ledOffMS = ledOffMs;
      notification.lights = true;
    }

    public void setInsistent(boolean insistent) {
      notification.insistent = insistent;
    }

    public void sendNotification() {
      if (notification == null)
        return;

      if (nm == null)
        nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);

      nm.notify(R.string.notification, notification);
    }

    public void cancelNotification() {
      if (nm == null)
        nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);

      nm.cancel(R.string.notification);
    }

    public void makeToast(String text, boolean lengthShort) {
      Toast.makeText(con, text, lengthShort ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();
    }
  }

  class JSISensor {
    Context con;
//    String sensor;
//    boolean available;

    public JSISensor(Context con) {
      this.con = con;
//      this.sensor = sensor;
//      available = available();
//      if (available)
//        Sensors.enableSensor(sensor);
    }

    public float[] read(String sensor) {
      try {
        float[] vals = readSensor(sensor);
        return vals;
      } catch (Exception e) {
        logD("couldn't read from " + sensor + ": " + e.getMessage());
      }
      return null;
    }

    public boolean available(String sensor) {
      return sensorIsAvailable(sensor);
    }

    public void enableSensor(String sensor) {
      try {
        Sensors.enableSensor(sensor);
      } catch (IllegalArgumentException i) {
        logD("couldn't enable sensor " + sensor + ", " + i.getMessage());
      }
    }

    public void setUpdateRate(String sensor, float updatesPerSecond) {
      setSensorUpdateRate(sensor, updatesPerSecond);
    }

    public float getUpdateRate(String sensor) {
      return getSensorUpdateRate(sensor);
    }

    public void unsetUpdateRate(String sensor) {
      unsetSensorUpdateRate(sensor);
    }
  }

//  class JSIOrientation extends JSISensor {
//    public JSIOrientation(Context con) {
//      super(con, Sensors.SENSOR_ORIENTATION);
//    }
//  }
//
//  class JSICompass extends JSISensor {
//    public JSICompass(Context con) {
//      super(con, Sensors.SENSOR_COMPASS);
//    }
//  }
//
//  class JSIAccelerometer extends JSISensor {
//    public JSIAccelerometer(Context con) {
//      super(con, Sensors.SENSOR_ACCELEROMETER);
//    }
//  }

  class JSIEventManager {
    Context con;
    private Hashtable<Event.Type, Boolean> subscriptions;
    public Event.Type eventType;

    public JSIEventManager(Context con) {
      this.con = con;
      subscriptions = new Hashtable<Event.Type, Boolean>();
    }

    public boolean isSubscribed(Type type) {
      return (subscriptions != null && subscriptions.containsKey(type) && subscriptions.get(type).booleanValue());
    }

    /**
     * events can be sent via a native even delivered to an event handler in JS or via
     * timer-based polling in JS
     * @return
     */
    public boolean isEventsViaTimer() {
      return true;
    }

    public void subscribe(String eventType) {
      logD("subscribing to event type: " + eventType);
      for (Event.Type type: Event.Type.values()) {
        if (type.toString().equals(eventType)) {
          logD("subscribed to event type: " + type.toString());
          subscriptions.put(type, true);

          if (type == Event.Type.geoLocation) {
            startReceivingLocUpdates();
//            startSendingLocUpdates();
          }
          else if (type == Event.Type.xmpp) {
            startReceivingXMPPMessages();
          }
        }
      }
    }

    public void unsubscribe(String eventType) {
      logD("ubsubscribing from event type: " + eventType.toString());
      for (Event.Type type: Event.Type.values()) {
        if (type.toString().equals(eventType)) {
          logD("unsubscribed from event type: " + type.toString());
          subscriptions.put(type, false);

          if (type == Event.Type.geoLocation) {
            stopReceivingLocationUpdates();
//            stopSendingLocationUpdates();
          }
          if (type == Event.Type.xmpp) {
            stopReceivingXMPPMessages();
          }
        }
      }
    }

    public void popEvent() {
      if (!isEventsViaTimer())
        return;

      if (eventQueue.isEmpty())
        return;

      Event event = null;
      try {
        event = eventQueue.take();
      } catch (InterruptedException i) {
        Bhoost.logD("event queue thread interrupted: " + i.getMessage());
//        if (!handleEvents)
//          return false;
      }

      logD("popped event of type: " + event.eventType);
      jsiEventManager.eventType = event.eventType;

//      if (!handleEvents) {
//        logD("stopping event queue thread");
//        return false;
//      }

//      return copyEvent(event);
      copyEvent(event);
    }

    public boolean hasEvent() {
      if (!isEventsViaTimer())
        return false;

      if (eventQueue == null)
        return false;

      synchronized(eventQueue) {
        return (!eventQueue.isEmpty());
      }
    }

    public void readyForNextEvent() {
      readySemaphore.release();
    }

    public void readyForEvents() {
      if (isEventsViaTimer())
        return;

      eventQueue = eventQueue == null ? new LinkedBlockingQueue<Event>() : eventQueue;
      eventQueueThread = new EventQueueThread();
      eventQueueThread.start();
      startSemaphore.release();
    }

    public String getEventType() {
      if (eventType != null)
        return eventType.toString();
      return null;
    }
  }

  class JSIKeyBoard {
    private Context con;

    public JSIKeyBoard(Context con) {
      this.con = con;
    }
  }

  class JSIKeyEvent extends Event {
    private Context con;
    public int keyCode;
    public int action;

    public JSIKeyEvent(Context con) {
      this.con = con;
      this.eventType = Event.Type.key;
    }

    public void copyFrom(Event event) {
      if (!(event instanceof JSIKeyEvent))
        throw new IllegalArgumentException("this is not a jsikeyevent!");

      JSIKeyEvent j = (JSIKeyEvent)event;
      keyCode = j.keyCode;
      action = j.action;
    }

    public int getKeyCode() {
      return keyCode;
    }

    public int getAction() {
      return action;
    }
  }

  class JSIBrowserHistory {
    private Context con;

    public JSIBrowserHistory(Context con) {
      this.con = con;
    }
  }

  class JSIBrowserHistoryEvent extends Event {
    private Context con;
    public String url;

    public JSIBrowserHistoryEvent(Context con) {
      this.con = con;
      this.eventType = Event.Type.browserHistory;
    }

    public void copyFrom(Event event) {
      if (!(event instanceof JSIBrowserHistoryEvent))
        throw new IllegalArgumentException("this is not a JSIBrowserHistoryEvent!");

      JSIBrowserHistoryEvent j = (JSIBrowserHistoryEvent)event;
      url = j.url;
    }

    public String getUrl() {
      return url;
    }

//    public boolean backButtonPressed() {
//      boolean pressed = BROWSER_BUTTON_PRESSED == BACK;
//      if (pressed)
//        BROWSER_BUTTON_PRESSED = NO_ACTION;
//
//      return pressed;
//    }
//
//    public boolean forwardButtonPressed() {
//      boolean pressed = BROWSER_BUTTON_PRESSED == FORWARD;
//      if (pressed)
//        BROWSER_BUTTON_PRESSED = NO_ACTION;
//
//      return pressed;
//    }
  }

  class JSIXmpp {
    private Context con;
//    private int notificationId = R.string.notification;

    public JSIXmpp(Context con) {
      this.con = con;
    }

//    public void init(long lastMessageTime) {
//      logD("init()");
//      if (service != null) {
//        try {
//          service.init(lastMessageTime);
//        } catch (DeadObjectException d) {
//          logD("failed to init: " + d.getMessage());
//        }
//      }
//    }

    public void setHost(final String domain) {
      logD("entered setHost");
      if (domain == null || domain.trim().length() == 0)
        return;

      if (service == null) {
        return;
      }

      try {
        logD("setting host to " + domain);
        service.setHost(domain);
      } catch (DeadObjectException d) {
        logD("failed to setHost: " + d.getMessage());
      }

//      Thread thr = new Thread() {
//        public void run() {
//          while (service == null) {
//            try {
//              Thread.sleep(1000);
//            } catch (InterruptedException i) {}
//          }
//          runOnUIThread(new Runnable() {
//            public void run() {
//              try {
//                logD("setting host to " + domain);
//                service.setHost(domain);
//              } catch (DeadObjectException d) {
//                logD("failed to setHost: " + d.getMessage());
//              }
//            }
//          });
//        }
//      };
//      thr.start();
    }

    public void setChatRoom(String name) {
      logD("setChatRoom(" + name + ")");
      if (name == null || name.trim().length() == 0)
        return;
      if (service != null) {
        try {
          service.setChatRoom(name);
        } catch (DeadObjectException d) {
          logD("failed to getChatRoom: " + d.getMessage());
        }
      }
    }

    public void sendMessage(String text, String to) {
      logD("sendMessage(" + text + ")");
      if (service != null) {
        try {
          service.sendMessage(text, to);
        } catch (DeadObjectException d) {
          logD("failed to sendMessage: " + d.getMessage());
        }
      }
    }

    public void loginCurrentUser() {
      logD("logging in current user");
      String username = jsiUser.getUserId();
      String password = jsiUser.getPassword();
      if (username == null || password == null)
        return;
      login(username, password);
    }

    public void login(String username, String password) {
      try {
        service.login(username, password);
      } catch (DeadObjectException d) {
        logD("failed to login:" + d.getMessage());
      }
    }

//    public void subscribe() {
//      jsiEventManager.subscribe(Event.Type.xmpp);
//    }
//
//    public void unsubscribe() {
//      jsiEventManager.unsubscribe(Event.Type.xmpp);
//    }
  }

  class JSIXmppEvent extends Event {
    private Context con;
    public XMPPMessage message;

    public JSIXmppEvent(Context con) {
      this.con = con;
      this.eventType = Event.Type.xmpp;
    }

    public void copyFrom(Event event) {
      if (!(event instanceof JSIXmppEvent))
        throw new IllegalArgumentException("this is not a jsixmppevent!");

      JSIXmppEvent j = (JSIXmppEvent)event;
      message = j.message;
    }

    public String getSender() {
      return message.sender;
//      String temp = message.sender;
//      if (temp.contains("/"))
//        temp = temp.substring(temp.lastIndexOf("/") + 1);
//      return temp;
    }

    public String getBody() {
      return message.body;
    }

    public long getTime() {
      return message.time;
    }

    public String getChatRoom() {
      return message.chatroom;
    }

    public String getMessageType() {
      return message.type;
    }
  }

  class JSILog {
    Context con;
    String jsiLogTag = "JSI_LOG";

    public JSILog(Context con) {
      this.con = con;
    }

    public void log(String msg) {
      Bhoost.logD(msg);
    }

    public void logDB() {
      CacheManager1.printToLog(con, homeDomain);
    }
  }

//  class JSIUploader {
//    Context con;
//
//    public JSIUploader(Context con) {
//      this.con = con;
//    }
//
//    public void upload(String localpath, String targetUrl) {
//      // upload to url
//    }
//  }
//
  class JSICamera {
    Context con;
    String path;
    String uploadUrl;

    public JSICamera(Context con) {
      this.con = con;
    }

    /**
     * bring up camera and allow user to take a picture. Picture will be saved to a local path corresponding one-to-one with writepath.
     * if user takes a picture, picture will be uploaded to uploadUrl
     * @param writepath
     * @param uploadUrl
     */
    private void takePicture(String writepath, String uploadUrl) {
      if (!isSubscribed(Event.Type.camera))
        return;

      this.path = getLocalPath(writepath);
      this.uploadUrl = uploadUrl;
//      File file = new File(path);
//      if (file.exists()) {
//        logD("file already exists, not allowed to overwrite");
//        return;
//      }
      createNeededDirs(path);
      startCameraActivity(path);
    }

    /**
     * bring up camera and allow user to take a picture. if user takes the picture, picture will be uploaded to uploadUrl
     * @param uploadUrl
     */
    public void takePicture(String uploadUrl) {
      takePicture("images/autopic.jpg", uploadUrl);
    }

    public byte[] getByteArray() {
      FileInputStream fis = null;
      File file = getFileStreamPath(path);
      if (file == null)
        return null;
      long length = file.length();
      byte[] bytes = new byte[(int)length];
      try {
         fis = new FileInputStream(file);
         if (fis == null)
           return null;

         int offset = 0;
         int numRead = 0;
         while (offset < bytes.length && (numRead = fis.read(bytes, offset, bytes.length-offset)) >= 0) {
           offset += numRead;
         }

         // Ensure all the bytes have been read in
         if (offset < bytes.length) {
             throw new IOException("Could not completely read file " + file.getName());
         }
         return bytes;
      } catch (FileNotFoundException f) {
        logD("failed to find file: " + path + ", " + f.getMessage());
        return null;
      } catch (IOException i) {
        logD("failed to read from file: " + path + ", " + i.getMessage());
        return null;
      } finally {
        if (fis != null) {
          try {fis.close();} catch (IOException i) {}
        }
      }
    }

//    public void subscribe() {
//      jsiEventManager.subscribe(Event.Type.camera);
//    }
//
//    public void unsubscribe() {
//      jsiEventManager.unsubscribe(Event.Type.camera);
//    }
  }

  class JSICameraEvent extends Event {
    String url;
    long length;
    Context con;

    public JSICameraEvent(Context con) {
      this.con = con;
      this.eventType = Event.Type.camera;
    }

    public void copyFrom(Event event) {
      if (!(event instanceof JSICameraEvent))
        throw new IllegalArgumentException("this is not a jsiCameraEvent!");

      JSICameraEvent j = (JSICameraEvent)event;
      url = j.url;
      length = j.length;
    }

    public String getUrl() {
      return url;
    }

    public long getLength() {
      return length;
    }
  }

  class JSILocation {
    Context con;

    public JSILocation(Context con) {
      this.con = con;
    }

    public boolean inRadius(double lat, double lon, long radius) { // radius in meters
      if (previousRecordedLoc == null) {
        if (locationManager != null)
          previousRecordedLoc = locationManager.getCurrentLocation("gps");
        else
          locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        return Bhoost.inProximity(previousRecordedLoc.getLatitude(), previousRecordedLoc.getLongitude(), lat, lon, radius);
      }
      return Bhoost.inProximity(previousRecordedLoc.getLatitude(), previousRecordedLoc.getLongitude(), lat, lon, radius);
    }

    public void setMinDistance(float meters) {
      minDistance = meters;
    }

    public void setMinTime(long millis) {
      minTime = millis;
    }
  }


  class JSILocationEvent extends Event {
    Context con;
    public double latitude;
    public double longitude;

    public JSILocationEvent(Context con) {
      this.con = con;
      this.eventType = Event.Type.geoLocation;
    }

    public double getLatitude() {
      return latitude;
    }

    public double getLongitude() {
      return longitude;
    }

    public void copyFrom(Event event) {
      if (!(event instanceof JSILocationEvent))
        throw new IllegalArgumentException("this is not a JSILocationEvent!");

      JSILocationEvent j = (JSILocationEvent)event;
      latitude = j.latitude;
      longitude = j.longitude;
    }
  }

  private String upload(String localPath, String uploadUrl, String contentType, long contentLength) {
    logD("attempting to upload to: " + uploadUrl);
    FileInputStream fis = null;
    OutputStream    out = null;
    uploadUrl = uploadUrl + '/' + System.currentTimeMillis() + ".jpg";
    try {
    URLConnection con;
    URL url = new URL(uploadUrl);
    con = url.openConnection();

    if (con instanceof HttpURLConnection) {
      ((HttpURLConnection)con).setRequestMethod("PUT");
    }
    else {
      logD("not HttpURLConnection, not uploading");
      return null;
    }
    con.setDoOutput(true);
    con.setRequestProperty("Content-Type", contentType);
    con.setRequestProperty("Content-Length", String.valueOf(contentLength));
    con.setRequestProperty("X-Create-Intermediate-Dirs", "true");

    File file = new File(localPath);
    fis = new FileInputStream(file);
    out = con.getOutputStream();
    copyStream(fis, out, contentLength);
    logD("finished writing to connection");
    } catch (MalformedURLException ex) {
      logD("bad url: " + uploadUrl + ", " + ex.getMessage());
      return null;
    } catch (IOException ioe) {
      logD("failed in writing to connection: " + ioe.getMessage());
      return null;
    } finally {
      try {
        if (fis != null)
          fis.close();
        if (out != null)
          out.close();
      } catch (IOException i) {}
    }

    return uploadUrl;
  }

  public static void copyStream(InputStream in, OutputStream out, long contentLength) throws IOException {
    int bufSize = contentLength / 4096 > 10000 ? 32768 : 4096; // use bigger buffer if contentLength is long
    byte[] buf = new byte[bufSize];
    long lenCnt = contentLength;
    for (; ; ) {
      int l = (int)Math.min(lenCnt, buf.length);
      int len = in.read(buf, 0, l);
      if (len == -1)
        break;
      out.write(buf, 0, len);
      lenCnt -= len;
      if (lenCnt == 0)
        break;
    }
    if (lenCnt != 0)
      throw new IOException("Encounted EOF with " + lenCnt + " bytes left to read. Content length was: " + contentLength);
  }

  public float getSensorUpdateRate(String sensor) {
    try {
      return Sensors.getSensorUpdateRate(sensor);
    } catch (Exception i) {
      logD("couldn't set update rate for " + sensor + ", " + i.getMessage());
      return -1;
    }
  }

  public void unsetSensorUpdateRate(String sensor) {
    try {
      Sensors.unsetSensorUpdateRate(sensor);
    } catch (Exception i) {
      logD("couldn't set update rate for " + sensor + ", " + i.getMessage());
    }
  }

  public void setSensorUpdateRate(String sensor, float updatesPerSecond) {
    try {
      Sensors.setSensorUpdateRate(sensor, updatesPerSecond);
    } catch (Exception i) {
      logD("couldn't set update rate for " + sensor + ", " + i.getMessage());
    }
  }

  public float[] readSensor(String sensor) {
    float[] out = null;
    try {
      /* Get number of sensor-values the sensor will return. Could be
       * variable, depending of the amount of axis (1D, 2D or 3D
       * accelerometer). */
      int sensorValues = Sensors.getNumSensorValues(sensor);
      out = new float[sensorValues];

      /* Make the OS fill the array we passed. */
      Sensors.readSensor(sensor, out);
    } catch (Exception i) {
      logD("couldn't read sensor data for " + sensor + ", " + i.getMessage());
    }
    /* And return it. */
    return out;
  }

  public boolean sensorIsAvailable(String sensor) {
    for (String aSensor : Sensors.getSupportedSensors()) {
      if (aSensor.equals(sensor)) {
        return true;
      }
    }
    return false;
  }

  private static boolean inProximity(double lat1, double lon1, double lat2, double lon2, long proximityThresh) { // threshold is in meters
    lat1 = 40.926 * Math.PI / 180;
    lon1 = -74.039 * Math.PI / 180;
    lat2 = 42.365 * Math.PI / 180;
    lon2 = -71.102 * Math.PI / 180;
    double radius = 6378100; // radius of the earth in meters (6,378.1 km)
    double dlon = lon2 - lon1;
    double dlat = lat2 - lat1;
    double temp1 = Math.sin(dlat/2);
    double temp2 = Math.sin(dlon/2);
    double a = temp1*temp1 + Math.cos(lat1) * Math.cos(lat2) * temp2 * temp2;
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    double d = radius * c;
    return d < proximityThresh;
  }

  private void createNeededDirs(String localPath) {
    StringBuilder sb = new StringBuilder(localPath);
    int lastSlashIndex = sb.lastIndexOf(File.separator);
    if (lastSlashIndex == -1)
      return;
    localPath = sb.substring(0, lastSlashIndex);
    File terminalDir = new File(localPath);
    terminalDir.mkdirs();
  }

  public String getLocalPath(String filename) {
    File base = getFileStreamPath("");
    String basepath = base.getAbsolutePath();
    return basepath + File.separator + filename;
  }
}
